﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data.Linq;
using System.Diagnostics;
using System.Linq.Expressions;

namespace PropertyExpression.CalcExpression
{
    //QQDO: вот такой юзкей: entity1.Entity2[3].Entity3
    //QDO: реализовать коллекции
    //QQDO: реализовать привязку к свойствам бизнес объектов -- байндер
    //QDO: свойство типа коллекции может меняться
    //QQDO: сабсвойства
    //QDO: проверить все области видимости. Где возможно поставить internal

    public interface ICalc<TEntity, TResult>
    {
        void AfterCalc();
        void Calc(TEntity entity);
        void BeforeCalc();
        IValue<TResult> Result { get; }
    }

    //QQDO: переименовать
    //QDO: возможно ввести интерфейс без TEntity
    public class NewPropertyHolder<TEntity, TProperty> where TEntity : INotifyPropertyChanged
    {
        public Expression<Func<TEntity, TProperty>> Expression
        {
            get { return expression; }
        }

        private readonly Expression<Func<TEntity, TProperty>> expression;
        private readonly Action onChangeAction;
        private readonly PropertyHolder<TEntity, TProperty> propertyHolder;

        internal NewPropertyHolder(
            TEntity entity, 
            Expression<Func<TEntity, TProperty>> expression,
            Action onChangeAction)
        {
            this.expression = expression;
            this.onChangeAction = onChangeAction;

            propertyHolder = new PropertyHolder<TEntity, TProperty>(entity, expression);

            entity.PropertyChanged += Entity_OnPropertyChanged;
        }

        private void Entity_OnPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == propertyHolder.PropertyName)
            {
                onChangeAction();
            }
        }
    }

    //public class DecimalValue : PropertyValue<decimal>
    //{
    //    internal DecimalValue(INotifyPropertyChanged entity, IPropertyHolder<decimal> propertyHolder)
    //        : base(entity, propertyHolder)
    //    {
    //    }
    //}

    //public class IntValue : PropertyValue<int>
    //{
    //    internal IntValue(INotifyPropertyChanged entity, IPropertyHolder<int> propertyHolder)
    //        : base(entity, propertyHolder)
    //    {
    //    }
    //}

    //public class ConstValue<TProperty>: IValue<TProperty>
    //{
    //    private readonly TProperty property;

    //    public ConstValue(TProperty property)
    //    {
    //        this.property = property;
    //    }

    //    public TProperty PrimVal
    //    {
    //        get 
    //        {
    //            return property;
    //        }
    //    }

    //    public event Action Changed;
    //}

    //QDO: переименовать. Возможно убрать.
    internal interface IPropertyHolder<TProperty>
    {
        string PropertyName { get; }
        TProperty GetValue();
    }

    //QDO: переименовать. Возможно убрать.
    internal class PropertyHolder<TEntity, TProperty> : IPropertyHolder<TProperty>
    {
        private readonly TEntity entity;
        private readonly Expression<Func<TEntity, TProperty>> expression;

        public PropertyHolder(TEntity entity, Expression<Func<TEntity, TProperty>> expression)
        {
            this.entity = entity;
            this.expression = expression;
        }

        public string PropertyName
        {
            get
            {
                return ((MemberExpression)expression.Body).Member.Name;
            }
        }

        public TProperty GetValue()
        {
            return expression.Compile()(entity);
        }
    }

    //QDO: устранить лишний пересчет, когда резельтат зависит от начального значения через несколько путей.
    //Реализация -- в IValueBase.Changed добавить аргумент. Аргумент тоже будет иметь событие. Событие агрумента
    //выполняется после выполнение события IValueBase.Changed. Binder подписывается на событие агрументом через специальный
    //класс (точнее это спец. класс подписывается). Внутри спец. класса счетчик. Обработчик
    //IValueBase.Changed инкрементирует счетчик, обпаботчик события аргумента декриментирует счетчик.
    //Собственно пересчет выполняется в обрабобтчике события агрумента, когда счетчик равен 0.
    public class CalcBinder
    {
        private readonly Queue<Action> queue = new Queue<Action>();

        public void Bind<TProperty>(Action<TProperty> setter, IValue<TProperty> value)
        {
            queue.Enqueue(delegate
                              {
                                  //Calc(setter, value);
                                  value.Changed += () => Calc(setter, value);
                              });
        }

        public void BeginCalc()
        {
            foreach (Action action in queue)
            {
                action();
            }
        }

        private static void Calc<TProperty>(Action<TProperty> setter, IValue<TProperty> value)
        {
            setter(value.PrimVal);
        }
    }

    //QQDO: подумать, что делать с Wrapper-ом
    //QQDO: убрать
    public class EntitySetWrapper<TEntity> : IEnumerable<TEntity> where TEntity : class
    {
        public void Add(TEntity entity)
        {
            entitySet.Add(entity);
            OnChanged();
        }

        private readonly EntitySet<TEntity> entitySet;

        //QDO: сделать создание через статический метод -- будет работать вывод типов
        public EntitySetWrapper(EntitySet<TEntity> entitySet)
        {
            this.entitySet = entitySet;
            this.entitySet.ListChanged += EntitySet_OnListChanged;
        }

        private void EntitySet_OnListChanged(object sender, ListChangedEventArgs e)
        {
            OnChanged();
        }

        internal void OnChanged()
        {
            if (CollectionChanged != null) CollectionChanged();
        }

        public event Action CollectionChanged;

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        public IEnumerator<TEntity> GetEnumerator()
        {
            return entitySet.GetEnumerator();
        }
    }

    //QQDO: подумать, что делать с Wrapper-ом
    public class EntityRefWrapper<TEntity, TProperty> where TEntity : class, INotifyPropertyChanged where TProperty : class
    {
        private readonly TEntity entity;
        private readonly Action<TProperty> onChangedAction;
        private readonly IPropertyHolder<TProperty> propertyHolder;

        private TProperty parentEntity;

        //QDO: сделать создание через статический метод -- будет работать вывод типов
        public EntityRefWrapper(TEntity entity, Expression<Func<TEntity, TProperty>> expression,
            Func<TProperty, EntitySetWrapper<TEntity>> entitySetWrapperFunc)
        {
            this.entity = entity;

            //QQDO:
            onChangedAction = x => entitySetWrapperFunc(x).OnChanged();

            propertyHolder = new PropertyHolder<TEntity, TProperty>(entity, expression);

            parentEntity = propertyHolder.GetValue();

            this.entity.PropertyChanged += Entity_OnPropertyChanged;
        }


        private void Entity_OnPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == propertyHolder.PropertyName)
            {
                if (parentEntity != null)
                {
                    onChangedAction(parentEntity);
                }
                parentEntity = propertyHolder.GetValue();
                if (parentEntity != null)
                {
                    onChangedAction(parentEntity);
                }
            }
        }
    }

    public class CalcCache
    {
        private readonly Action<string> onChangeAction;

        public CalcCache(Action<string> onChangeAction)
        {
            this.onChangeAction = onChangeAction;
        }

        readonly Dictionary<string, object> cache = new Dictionary<string, object>();

        public TProperty Return<TProperty>(Func<IValue<TProperty>> valueFunc)
        {
            string propertyName = new StackTrace().GetFrame(1).GetMethod().Name.Substring(4);
            IValue<TProperty> value;
            object tempValue;
            if (cache.TryGetValue(propertyName, out tempValue))
            {
                value = (IValue<TProperty>) tempValue;
            }
            else
            {
                value = valueFunc();
                cache.Add(propertyName, value);
                value.Changed +=
                    delegate { onChangeAction(propertyName); };
            }
            return value.PrimVal;
        }
    }

}